package container

import (
	"JoDocker/util"
	"fmt"
	"github.com/Sirupsen/logrus"
	"os"
	"os/exec"
	"syscall"
	"time"
)

var (
	RootPath                   = "/var/jodocker/"
	ReadPath                   = RootPath + "images/%s"      // images/镜像名
	MntPath             string = RootPath + "mnt/%s"         // mnt/容器名
	OverlayPath                = RootPath + "over/%s/"       // over/容器名
	WriteLayerPath      string = OverlayPath + "writeLayer/" // over/容器名/writeLayer
	WorkPath                   = OverlayPath + "work/"       // over/容器名/work
	RUNNING                    = "running"
	STOP                       = "stopped"
	EXIT                       = "exited"
	DefaultInfoLocation        = "/var/run/jodocker/%s/"
	ConfigFileName             = "config.json"
	ContainerLogFile    string = "container.log"
)

type ContainerInfo struct {
	Pid         string   `json:"pid"`         // 容器init进程在宿主机上的PID
	Id          string   `json:"id"`          // 容器id
	Name        string   `json:"name"`        // 容器名
	Command     string   `json:"command"`     // 容器内init运行命令
	CreatedTime string   `json:"createTime"`  // 创建时间
	Status      string   `json:"status"`      // 容器的状态
	Volume      string   `json:"volume"`      // 数据卷
	PortMapping []string `json:"portmapping"` // 端口映射
}

func MakeContainerInfo(id string, pid string, command string, name string, volume string) *ContainerInfo {
	this := &ContainerInfo{
		Pid:         pid,
		Id:          id,
		Name:        name,
		Command:     command,
		CreatedTime: time.Now().Format("2006-01-02 15:04:05"),
		Status:      RUNNING,
		Volume:      volume,
	}
	return this
}

func NewParentProcess(tty bool, containerName string, volume string, imageName string, envSlc []string) (*exec.Cmd, *os.File) {
	readPipe, writePipe, err := NewPipe()
	if err != nil {
		logrus.Errorf("New pipe error %v", err)
		return nil, nil
	}
	// 调用自己
	cmdPtr := exec.Command("/proc/self/exe", "init")
	cmdPtr.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}
	// -ti 参数 交互形式
	if tty {
		cmdPtr.Stdin = os.Stdin
		cmdPtr.Stdout = os.Stdout
		cmdPtr.Stderr = os.Stderr
	} else {
		dirPath := fmt.Sprintf(DefaultInfoLocation, containerName)
		if err := os.MkdirAll(dirPath, 0622); err != nil {
			logrus.Errorf("NewParentProcess mkdir %s error %v", dirPath, err)
			return nil, nil
		}
		stdLogFilePath := dirPath + ContainerLogFile
		stdLogFile, err := os.Create(stdLogFilePath)
		if err != nil {
			logrus.Errorf("NewParentProcess create file %s error %v", stdLogFilePath, err)
			return nil, nil
		}
		// 终端输出指定到日志文件中.
		cmdPtr.Stdout = stdLogFile
	}
	// 读的管道赋值给子进程, 外带读管道去创建子进程.
	cmdPtr.ExtraFiles = []*os.File{readPipe}

	// 宿主机的环境变量+自定义环境变量
	cmdPtr.Env = append(os.Environ(), envSlc...)

	// 创建联合文件和数据卷挂载
	NewWorkSpace(imageName, containerName, volume)

	// 容器init后, 指定工作目录
	containerWorkDirPath := fmt.Sprintf(MntPath, containerName)

	logrus.Infof("cmd.dir => 容器工作目录: %s", containerWorkDirPath)

	cmdPtr.Dir = containerWorkDirPath
	return cmdPtr, writePipe
}

// NewPipe 创建一个匿名管道
func NewPipe() (*os.File, *os.File, error) {
	readFilePtr, writeFilePtr, err := os.Pipe()
	if err != nil {
		return nil, nil, err
	}
	return readFilePtr, writeFilePtr, nil
}

func NewWorkSpace(imageName string, containerName string, volume string) {

	// 读层
	//readDirPath := rootPath + "busybox/"
	//tarPath := rootPath + "busybox.tar"
	readDirPath := fmt.Sprintf(ReadPath, imageName)
	tarPath := readDirPath + ".tar"
	CreateReadOnlyLayer(readDirPath, tarPath)

	logrus.Infof("挂载 => 镜像路径: %s,  tar包路径:%s", readDirPath, tarPath)

	// 挂载层
	//overlayPath := rootPath + "over/"
	overlayPath := fmt.Sprintf(OverlayPath, containerName)
	CreateOverlayDirAdnTmpFsMount(overlayPath)

	logrus.Infof("挂载 => overlay路径: %s", overlayPath)

	// 读写层
	//writeDirPath := overlayPath + "writeLayer/"
	writeDirPath := fmt.Sprintf(WriteLayerPath, containerName)
	CreateWriteLayer(writeDirPath)

	logrus.Infof("挂载 => write路径: %s", writeDirPath)

	// 工作层
	//workDirPath := overlayPath + "work/"
	workDirPath := fmt.Sprintf(WorkPath, containerName)
	CreateWorker(workDirPath)

	logrus.Infof("挂载 => work路径: %s", workDirPath)

	// overlay挂载
	mntPath := fmt.Sprintf(MntPath, containerName)
	CreateMountPoint(readDirPath, writeDirPath, workDirPath, mntPath)

	logrus.Infof("挂载 => mnt路径: %s", mntPath)

	// 数据卷挂载
	if volume != "" {
		volumeSlc := util.StrToSlc(volume, ":")
		if len(volumeSlc) == 2 && volumeSlc[0] != "" && volumeSlc[1] != "" {
			mountVolumeForBind(mntPath, volumeSlc)
		} else {
			logrus.Infof("Volume parameter input is not correct.")
		}
	}
}

// CreateReadOnlyLayer 创建读层的目录
func CreateReadOnlyLayer(readDirPath string, tarPath string) {
	exist, err := util.PathExists(readDirPath)
	if err != nil {
		logrus.Infof("error => Fail to judge whether dir %s exists. %v", readDirPath, err)
	}
	if exist == false {
		if err := os.MkdirAll(readDirPath, 0777); err != nil {
			logrus.Errorf("error => Mkdir dir %s error. %v", readDirPath, err)
		}
		// 解压tar包
		if _, err := exec.Command("tar", "-xvf", tarPath, "-C", readDirPath).CombinedOutput(); err != nil {
			logrus.Errorf("error => Untar dir %s error %v", readDirPath, err)
		}
	}
}

// CreateWriteLayer 创建读写层的目录
func CreateWriteLayer(writeDirPath string) {
	if exist, _ := util.PathExists(writeDirPath); !exist {
		if err := os.MkdirAll(writeDirPath, 0777); err != nil {
			logrus.Errorf("error => Mkdir dir %s error. %v", writeDirPath, err)
		}
		logrus.Infof("创建write层目录 %s", writeDirPath)
	}
}

// CreateWorker 创建操作过程目录
func CreateWorker(workDirPath string) {
	if exist, _ := util.PathExists(workDirPath); !exist {
		if err := os.MkdirAll(workDirPath, 0777); err != nil {
			logrus.Errorf("error => Mkdir dir %s error. %v", workDirPath, err)
		}
		logrus.Infof("创建work层目录 %s", workDirPath)
	}
}

// CreateMountPoint overlay挂载
func CreateMountPoint(readDirPath, writeDirPath, workDirPath, mntPath string) {
	if err := os.MkdirAll(mntPath, 0777); err != nil {
		logrus.Errorf("error => Mkdir dir %s error. %v", mntPath, err)
	}

	logrus.Infof("创建mnt层目录 %s", mntPath)

	lowerDir := "lowerdir=" + readDirPath
	upperDir := "upperdir=" + writeDirPath
	workDir := "workdir=" + workDirPath
	dirs := lowerDir + "," + upperDir + "," + workDir

	logrus.Infof("overlay挂载 dirs: %s, mnt: %s", dirs, mntPath)

	cmd := exec.Command("mount", "-t", "overlay", "overlay", "-o", dirs, mntPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		logrus.Errorf("error => mount overlay %v", err)
	}
}

// CreateOverlayDirAdnTmpFsMount 挂载临时文件系统
func CreateOverlayDirAdnTmpFsMount(overlayPath string) {
	if exist, _ := util.PathExists(overlayPath); !exist {
		if err := os.MkdirAll(overlayPath, 0777); err != nil {
			logrus.Errorf("error => Mkdir dir %s error. %v", overlayPath, err)
		}
		logrus.Infof("创建overlay层目录 %s", overlayPath)
	}
	// 临时的文件系统
	if _, err := exec.Command("mount", "-t", "tmpfs", "tmpfs", overlayPath).CombinedOutput(); err != nil {
		logrus.Errorf("error => mount tmpfs %s error %v", overlayPath, err)
	}
}

func DeleteWorkSpace(containerName string, volume string) {
	//overlayPath := rootPath + "over/"
	//mntPath := rootPath + "mnt/"
	//writeDirPath := overlayPath + "writeLayer/"

	mntPath := fmt.Sprintf(MntPath, containerName)
	overlayPath := fmt.Sprintf(OverlayPath, containerName)
	writeDirPath := fmt.Sprintf(WriteLayerPath, containerName)

	// 取消数据卷挂载
	if volume != "" {
		volumeSlc := util.StrToSlc(volume, ":")
		umountVolumeForBind(mntPath, volumeSlc)
	}

	// 取消联合文件系统挂载
	umountOverlay(overlayPath, mntPath)

	deleteMnt(mntPath)

	deleteWriteLayer(writeDirPath)

	logrus.Infof("资源释放!!!")
}

func umountOverlay(overlayPath string, mntPath string) {

	if _, err := exec.Command("umount", overlayPath).CombinedOutput(); err != nil {
		logrus.Errorf("error => umount %s error %v", overlayPath, err)
	}

	if _, err := exec.Command("umount", mntPath).CombinedOutput(); err != nil {
		logrus.Errorf("error => umount %s error %v", mntPath, err)
	}
}

func deleteMnt(mntPath string) {
	if err := os.RemoveAll(mntPath); err != nil {
		logrus.Errorf("error => remove dir %s error %v", mntPath, err)
	}
}

func deleteWriteLayer(writeLayerPath string) {
	if err := os.RemoveAll(writeLayerPath); err != nil {
		logrus.Errorf("error => remove dir %s error %v", writeLayerPath, err)
	}
}
